'use strict';

import {app, protocol, BrowserWindow, Menu, ipcMain, dialog, session, globalShortcut} from 'electron';
import {createProtocol} from 'vue-cli-plugin-electron-builder/lib';

const isDevelopment = process.env.NODE_ENV !== 'production';
const fs = require('fs');
const path = require('path');
const sd = require('silly-datetime');
const sqlite3 = require('sqlite3').verbose();
const {spawn} = require('child_process');
const http = require('http');
const winston = require('winston');
const Store = require('electron-store');

let prefixUrl = ''
if (process.env.NODE_ENV !== 'development') {
    prefixUrl = process.resourcesPath
    app.setPath('userData', path.resolve(process.resourcesPath, '../', 'userData'));
} else {
    // 这里放本地代码中static文件夹的路径
    prefixUrl = 'D:\\work\\HTHT\\Viewer\\3d-viewer\\static'
}

// 创建electron数据仓库
const store = new Store({
    cwd: path.resolve(app.getPath('userData'), 'config')
});

// 初始化时判断仓库中是否有设置信息，如果没有，需要创建
if (!store.get('appSettings')) {
    store.set('appSettings', null)
}

// 创建日志文件
const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
        new winston.transports.File({filename: path.resolve(app.getPath('userData'), 'log', 'app.log')}),
        new winston.transports.Console()
    ]
});


app.commandLine.appendSwitch('force_high_performance_gpu'); // 有独显时启动独显渲染

// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
    {scheme: 'app', privileges: {secure: true, standard: true}}
]);

let win = null

async function createWindow() {
    // Create the browser window.
    win = new BrowserWindow({
        show: false,
        width: 962,
        height: 572,
        minWidth: 962,
        minHeight: 572,
        icon: `${__static}/icon.ico`,
        webPreferences: {
            webSecurity: false, // 允许跨域
            backgroundColor: '#FFFFFF',
            nodeIntegration: true, // 渲染层可以使用node
            contextIsolation: false,
        }
    });
    win.maximize();
    win.show();
    // 注册快捷键
    globalShortcut.register('CommandOrControl+Shift+I', () => {
        win.webContents.openDevTools();
    });

    if (process.env.WEBPACK_DEV_SERVER_URL) {
        await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
        // if (!process.env.IS_TEST) win.webContents.openDevTools();
    } else {
        createProtocol('app');
        // Load the index.html when not in development
        win.loadURL('app://./index.html');
        // win.webContents.openDevTools();
    }

    createMenu();
    getReSize(win);
    const filter = {
        urls: ['app://cesium/*']
    };
    session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => {
        let url = details.url.replace('app://cesium/', 'app://./cesium/')
        callback({redirectURL: url});
    });
    const tdtFilter = {
        urls: ['*://*.tianditu.gov.cn/*']
    }
    session.defaultSession.webRequest.onBeforeSendHeaders(tdtFilter, (details, callback) => {
        details.requestHeaders['Referer'] = 'https://localhost:8080';
        callback({cancel: false, requestHeaders: details.requestHeaders});
    });
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
    // On macOS it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
    createWindow();
    nodeServer()
});

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
    if (process.platform === 'win32') {
        process.on('message', (data) => {
            if (data === 'graceful-exit') {
                app.quit();
            }
        });
    } else {
        process.on('SIGTERM', () => {
            app.quit();
        });
    }
}

// 设置菜单栏
function createMenu() {
    // darwin表示macOS，针对macOS的设置
    if (process.platform === 'darwin') {
        const template = [{
            label: 'Electron',
            submenu: [{
                role: 'about'
            }, {
                role: 'quit'
            }]
        }];
        const menu = Menu.buildFromTemplate(template);
        Menu.setApplicationMenu(menu);
    } else {
        // windows及linux系统
        Menu.setApplicationMenu(null);
    }
}

// 监听窗口缩放
function getReSize(win) {
    if (win) {
        win.on('resize', () => {
            const size = win.getSize();
            win.webContents.send('resizeEvent', JSON.stringify(size));
        });
    }
}

mainProcessEvents()

function mainProcessEvents() {
    // 捕获重新启动事件
    ipcMain.on('restart-app', () => {
        // app.relaunch();
        // app.exit(0);
        win.reload();
    })
    // 调用electron-store中的get方法
    ipcMain.on('electron-store:get', (event) => {
        event.sender.send('electron-store:get', store.get('appSettings'))
    });
    // 调用electron-store中的set方法
    ipcMain.on('electron-store:set', (event, data) => {
        try {
            store.set('appSettings', data);
            event.sender.send('electron-store:set', {
                type: 'success',
                msg: '设置保存成功'
            })
        } catch (e) {
            event.sender.send('electron-store:set', {
                type: 'error',
                msg: '设置保存失败'
            })
        }
    });
    // 保存日志
    ipcMain.on('save-log', (event, data) => {
        const {type, message} = data
        logger[type](message)
    })

    // 打开文件夹
    ipcMain.on('show-open-dialog', (event, data = {}) => {
        const option = {
            title: data.title || '选择要上传的文件',
            filters: data.filters || [{name: 'file', extensions: ['json']}],
            buttonLabel: data.buttonLabel || '打开'
        };
        dialog.showOpenDialog(option).then((res) => {
            const filePath = res.filePaths[0];
            let fileSize = null;
            let fileContent = null;
            if (filePath) {
                const stat = fs.statSync(filePath);
                fileSize = stat.size / 1024 / 1024;
                if (fileSize <= 10) {
                    const readFileSync = fs.readFileSync(filePath, 'utf-8');
                    fileContent = readFileSync.toString();
                }
            }
            event.sender.send('show-open-dialog', {filePath, fileSize, fileContent});
        });
    });

    // 保存文件夹
    ipcMain.on('show-save-dialog', (event, data) => {
        const {fileContent} = data;
        const options = {
            title: "保存文件",
            defaultPath: `C:\\新建文件_${sd.format(new Date(), 'YYYY-MM-DD-HH-mm-ss')}.3dvp`,
            filters: [
                {name: '3DVP', extensions: ['3dvp']}
            ]
        };
        dialog.showSaveDialog(options).then(result => {
            const {canceled, filePath} = result;
            if (!canceled && filePath) {
                fs.writeFileSync(result.filePath, fileContent);
                const stat = fs.statSync(filePath);
                const fileSize = stat.size / 1024 / 1024;
                event.sender.send('show-save-dialog', {filePath, fileSize, fileContent});
            }
        }).catch(err => {
            logger.info(err)
        });
    });
    // 打开选择文件夹
    ipcMain.on('open-directory-dialog', (event, data) => {
        const option = {
            title: data.title || '选择要上传的文件',
            filters: data.filters || [],
            buttonLabel: data.buttonLabel || '打开',
            properties: data.properties || ['openDirectory']
        };
        dialog.showOpenDialog(option).then(res => {
            let folderPath = null;
            if (data.properties.indexOf('multiSelections') > -1) {
                folderPath = res.filePaths;
            } else {
                folderPath = res.filePaths[0];
            }
            event.sender.send('open-directory-dialog', {folderPath});
        })
    })
    // 调用spawn执行exe文件
    ipcMain.on('spawn-exe', (event, data) => {
        try {
            const {exeName, configName, config, isSave = false, type = ''} = data
            const configPath = path.resolve(prefixUrl, exeName, `${configName}.json`)
            const exePath = path.resolve(prefixUrl, exeName)
            const command = `${exeName} -C ${configName}.json`
            // 在进行osgb追加的时候需要删除输出目录下的一些文件夹和文件
            if (type) {
                checkStepFinishLog(config)
            }

            // 追加模式需要给第一次处理结果做分组和快捷方式映射
            if (type !== 'replace' && type !== '') {
                handleShortcuts(config)
            }

            fs.writeFileSync(configPath, config);
            // 文件存在，可以执行
            const cmd = spawn('cmd', ['/C', command], {cwd: exePath});
            // 监听命令行输出
            cmd.stdout.on('data', (data) => {
                event.sender.send('spawn-exe', {info: data.toString('utf-8'), type: 'success'});
            });
            // 监听命令行错误输出
            cmd.stderr.on('data', (data) => {
                event.sender.send('spawn-exe', {info: `错误：${data.toString('utf-8')}`, type: 'error'});
            });
            cmd.on('close', (code) => {
                event.sender.send('spawn-exe', 'close');
                if (isSave) {
                    saveConfig(config)
                }
            })
        } catch (error) {
            event.sender.send('spawn-exe', {info: error, type: 'error'});
        }
    })
    // 退出
    ipcMain.on('app-exit', (event, data) => {
        app.exit();
    });
}

// 将转换参数保存到输出文件夹里
function saveConfig(config) {
    const {tileRootDir} = JSON.parse(config)
    const saveUrl = path.resolve(tileRootDir, 'osgb.conf')
    try {
        fs.writeFileSync(saveUrl, config)
    } catch (error) {
        logger.info(error)
    }
}

// 删除输出目录下的stepFinishLog.txt、merge文件夹、tem文件夹
function checkStepFinishLog(config) {
    const {tileRootDir} = JSON.parse(config)
    const txtUrl = path.resolve(tileRootDir, 'stepFinishLog.txt')
    const mergeUrl = path.resolve(tileRootDir, 'merge')
    const tempUrl = path.resolve(tileRootDir, '_tmp_')
    if (fs.existsSync(txtUrl)) {
        try {
            fs.unlinkSync(txtUrl);
            logger.info('删除stepFinishLog.txt成功')
        } catch (error) {
            logger.info(error)
        }
    }
    if (fs.existsSync(mergeUrl)) {
        try {
            fs.rmSync(mergeUrl, {recursive: true, force: true});
            logger.info('删除merge文件夹成功')
        } catch (error) {
            logger.info(error)
        }
    }
    if (fs.existsSync(tempUrl)) {
        try {
            fs.rmSync(tempUrl, {recursive: true, force: true});
            logger.info('删除tmp文件夹成功')
        } catch (error) {
            logger.info(error)
        }
    }
}

/**
 * 处理快捷方式的函数。
 * 根据配置信息，检查是否存在osgb.conf文件，如果存在，则根据之前的转换配置，
 * 对文件夹进行链接，以创建快捷方式。
 *
 * @param {string} config - 包含tilerRootDir和osgInput的配置信息的JSON字符串。
 */
function handleShortcuts(config) {
    const {tileRootDir, osgInput} = JSON.parse(config)
    if (!fs.existsSync(path.resolve(tileRootDir, 'osgb.conf'))) {
        return
    }
    // 先从osgb.conf中读取到前一次的转换配置信息
    const oldConfig = JSON.parse(fs.readFileSync(path.resolve(tileRootDir, 'osgb.conf'), 'utf-8'))

    // 只有当第一次转换时只有一组数据才会需要做映射
    const oldOsgInputKeys = Object.keys(oldConfig.osgInput)
    if (oldOsgInputKeys.length === 1) {
        // 创建第一次转换结果的组，把映射放到组里
        const groupUrl = path.resolve(tileRootDir, oldOsgInputKeys[0])
        if (!fs.existsSync(groupUrl)) {
            fs.mkdirSync(groupUrl)
        }
        const files = fs.readdirSync(tileRootDir);
        files.forEach(file => {
            const filePath = path.join(tileRootDir, file)
            const stats = fs.statSync(filePath);
            if (stats.isDirectory() && filePath !== groupUrl) {
                fs.symlinkSync(filePath, path.join(groupUrl, file), 'junction');
            }
        })
    }
}

function nodeServer() {
    // 监听端口
    const PORT = 8899;

    let paramsData = {}

    // 封装数据库操作
    const initDatabase = (dbUrl, serviceName) => {
        return new Promise((resolve, reject) => {
            const dbBase = new sqlite3.Database(dbUrl, (err) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(dbBase);
            });
        });
    };

    const queryDatabase = (dbBase, query, params) => {
        return new Promise((resolve, reject) => {
            dbBase.all(query, params, (err, rows) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(rows);
            });
        });
    };

    // 创建 HTTP 服务器
    const server = http.createServer(async (req, res) => {
        // 设置响应头
        res.setHeader('Content-Type', 'application/json');

        // 根据请求路径和方法进行路由处理
        if (req.method === 'POST' && req.url === '/createServer') {
            // 接收请求体数据
            let body = ''
            req.on('data', (chunk) => {
                body += chunk.toString();
            })
            // 请求体数据接收完毕
            req.on('end', async () => {
                // 解析 JSON 格式的请求体数据
                const {serviceName, dbUrl} = JSON.parse(body);
                // 参数验证
                if (!serviceName || !dbUrl) {
                    res.statusCode = 400
                    res.end(JSON.stringify({code: 400, msg: 'Bad Request: missing parameters'}))
                    return
                }
                let resData = {}
                if (paramsData[serviceName] && paramsData[serviceName].dbUrl === dbUrl) {
                    resData = {
                        code: 200,
                        msg: '服务已存在',
                        data: paramsData[serviceName].url
                    }
                    res.statusCode = 200;
                    res.end(JSON.stringify(resData))
                } else {
                    paramsData[serviceName] = {}
                    try {
                        const dbBase = await initDatabase(dbUrl, serviceName);
                        const rows = await queryDatabase(dbBase, `SELECT COUNT(*) FROM tiles WHERE filename = 'merge_tile.json'`, []);
                        if (rows[0]['COUNT(*)'] === 0) {
                            paramsData[serviceName] = {
                                dbUrl: dbUrl,
                                dbBase: dbBase,
                                url: `http://localhost:${PORT}/modelServer/${encodeURIComponent(serviceName)}/tileset.json`
                            };
                        } else {
                            paramsData[serviceName] = {
                                dbUrl: dbUrl,
                                dbBase: dbBase,
                                url: `http://localhost:${PORT}/modelServer/${encodeURIComponent(serviceName)}/merge_tile.json`
                            };
                        }
                        resData = {
                            code: 200,
                            msg: '服务创建成功',
                            data: paramsData[serviceName].url
                        }
                        res.statusCode = 200;
                        res.end(JSON.stringify(resData))
                    } catch (error) {
                        res.statusCode = 500;
                        res.end(JSON.stringify({code: 500, msg: error}));
                    }
                }
            });
        } else if (req.method === 'GET' && req.url.indexOf('/modelServer/') > -1) {
            const paths = req.url.replace('/modelServer/', '').split('/');
            const serviceName = decodeURIComponent(paths[0]);
            let fileName = paths.splice(1).join('\\')
            fileName = fileName.replaceAll('+', ' ')
            const suffix = fileName.split('.')[1]
            if (suffix === 'json') {
                res.setHeader('Content-Type', 'application/json')
            } else {
                res.setHeader('Content-Type', 'application/x-protobuf')
            }
            try {
                const rows = await queryDatabase(paramsData[serviceName].dbBase, `SELECT content FROM tiles WHERE filename = '${decodeURIComponent(fileName)}'`, [])
                res.statusCode = 200
                res.end(rows[0].content)
            } catch (error) {
                res.statusCode = 500;
                res.end(JSON.stringify({code: 500, msg: error}));
            }
        } else {
            // 处理未匹配的请求
            res.statusCode = 404;
            res.end(JSON.stringify({error: 'Not Found'}));
        }
    });

    server.listen(PORT, () => {
        logger.info(`node服务已启动，服务端口为 ${PORT}`)
    });
}



